﻿using System;
using System.Linq;
using System.Xml.Linq;
using Microsoft.DirectX;

namespace WolfGame {

  /// <summary>
  /// Class to represent the player and all of its properties
  /// </summary>
  class e_Player : e_EntityBase {

    #region Member Variables

    private float multiplier;                       //used for setting the "fatness" of the player
    private float sprintSpeed;                      //sprinting speed multiplier
    private Vector3 futurePos;                      //future position of player
    private float speed;                            //speed of the player
    private float scale;


    #endregion

    #region Constructor

    /// <summary>
    /// Instantiate the class
    /// </summary>
    /// <param name="common"></param>
    /// <param name="fileName"></param>
    /// <param name="pos"></param>
    public e_Player(Common common, string fileName, Vector3 pos)
      : base(common, fileName, pos) {

    }

    #endregion

    #region Loading

    /// <summary>
    /// Load the player properties
    /// </summary>
    public override void LoadBasicProperties() {
      try {
        Console.WriteLine("Loading player...");

        string entityPath = System.IO.File.ReadAllText(String.Format(common.EntityPath,
          System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location),
          fileName));

        XDocument properties = XDocument.Parse(entityPath);

        float spriteWidth, spriteHeight;

        spriteHeight = float.Parse((from ss in properties.Descendants("properties")
                                    select ss.Attribute("spriteHeight").Value).First());
        spriteWidth = float.Parse((from ss in properties.Descendants("properties")
                                   select ss.Attribute("spriteWidth").Value).First());
        scale = float.Parse((from ss in properties.Descendants("properties")
                             select ss.Attribute("scale").Value).First());

        Height = Int32.Parse((from ss in properties.Descendants("properties")
                              select ss.Attribute("height").Value).First());
        Width = Int32.Parse((from ss in properties.Descendants("properties")
                             select ss.Attribute("width").Value).First());

        // Create the rendering surface and set is properties
        Surface = new m_RenderableSurface(common);
        Surface.X1 = Position.X - (scale * spriteWidth) * .5f;
        Surface.X2 = Position.X + (scale * spriteWidth) * .5f;
        Surface.Y1 = Position.Y;
        Surface.Y2 = Position.Y + scale * spriteHeight;
        Surface.Z1 = Position.Z - (scale * spriteWidth) * .5f;
        Surface.Z2 = Surface.Z1;

        string texture = (from ss in properties.Descendants("properties")
                          select ss.Attribute("texture").Value).First();

        // Set the texture
        Surface.LoadTextures(texture);

        speed = float.Parse((from ss in properties.Descendants("movement")
                             select ss.Attribute("moveSpeed").Value).First());
        sprintSpeed = float.Parse((from ss in properties.Descendants("movement")
                                   select ss.Attribute("sprintSpeed").Value).First());
        TurnSpeed = float.Parse((from ss in properties.Descendants("movement")
                                 select ss.Attribute("turnSpeed").Value).First());
      }
      catch (Exception ex) {
        Console.WriteLine("Error loading basic entity properties.  " + ex.ToString());
      }
    }

    #endregion

    #region Movement

    /// <summary>
    /// Move the player forward or backward
    /// </summary>
    /// <param name="sprint"></param>
    /// <param name="direction"></param>
    public void MoveFrontOrBack(bool sprint, int direction) {
      //Set the future position
      if (sprint)
        multiplier = sprintSpeed * direction;
      else
        multiplier = speed * direction;

      futurePos.Y = Position.Y + multiplier * (float)Math.Sin(VRadians);
      futurePos.X = Position.X + multiplier * (float)(Math.Cos(VRadians) * Math.Cos(HRadians));
      futurePos.Z = Position.Z + multiplier * (float)(Math.Cos(VRadians) * Math.Sin(HRadians));

      // Check what the actual movement will be
      //actualMovement = common.Collider.collision(pos, futurePos, radius);
      Vector3 actualMovement = common.Collider.CalculateMovement(Position, futurePos, Width, Height);

      if (sprint)
        multiplier = sprintSpeed * direction;
      else
        multiplier = speed * direction;

      // If we can move, then do it
      float xNew = Position.X;
      float zNew = Position.Z;
      float yNew = Position .Y;
      if (actualMovement.X != 0)
        xNew = Position.X + multiplier * (float)(Math.Cos(VRadians) * Math.Cos(HRadians));
      if (actualMovement.Y != 0)
        yNew = Position.Y - common.Gravity;
      if (actualMovement.Z != 0)
        zNew = Position.Z + multiplier * (float)(Math.Cos(VRadians) * Math.Sin(HRadians));

      Position = new Vector3(xNew, yNew, zNew);
    }

    /// <summary>
    /// Move the player perpendicular to the direction they are facing
    /// </summary>
    /// <param name="sprint"></param>
    /// <param name="direction"></param>
    public void strafe(Boolean sprint, int direction) {
      if (sprint)
        multiplier = Width * sprintSpeed * direction;
      else
        multiplier = Width * speed * direction;

      futurePos.X = Position.X + multiplier * (float)Math.Cos(HRadians + Math.PI / 2);
      futurePos.Z = Position.Z + multiplier * (float)Math.Sin(HRadians + Math.PI / 2);

      // Check if we can move
      //actualMovement = common.Collider.collision(pos, futurePos, radius);

      if (sprint)
        multiplier = sprintSpeed * direction;
      else
        multiplier = speed * direction;

      // If there are movements to apply, then do it
      float xNew = Position.X;
      float zNew = Position.Z;
      //if (actualMovement.X != 0)
      xNew += multiplier * (float)Math.Cos(HRadians + Math.PI / 2);
      //if (actualMovement.Z != 0)
      zNew += multiplier * (float)Math.Sin(HRadians + Math.PI / 2);

      Position = new Vector3(xNew, Position.Y, zNew);
    }

    public override void Update() {
      if (Camera != null)
        common.Camera.setCamera(Position, HRadians, VRadians);
    }

    #endregion

    #region Properties

    public v_Camera Camera { get; set; }
    public float HRadians { get; set; }
    public float VRadians { get; set; }
    public float TurnSpeed { get; set; }

    #endregion
  }
}
